#!/usr/bin/perl
#
# Generate lwlocknames.h from lwlocklist.h
# Copyright (c) 2000-2025, PostgreSQL Global Development Group

use strict;
use warnings FATAL => 'all';
use Getopt::Long;

my $output_path = '.';

my $lastlockidx = -1;

GetOptions('outdir:s' => \$output_path);

open my $lwlocklist, '<', $ARGV[0] or die;
open my $wait_event_names, '<', $ARGV[1] or die;

# Include PID in suffix in case parallel make runs this multiple times.
my $htmp = "$output_path/lwlocknames.h.tmp$$";
open my $h, '>', $htmp or die "Could not open $htmp: $!";

my $autogen =
  "/* autogenerated from src/include/storage/lwlocklist.h, do not edit */\n";
print $h $autogen;
print $h "/* there is deliberately not an #ifndef LWLOCKNAMES_H here */\n\n";


#
# First, record the predefined LWLocks and built-in tranches listed in
# wait_event_names.txt.  We'll cross-check those with the ones in lwlocklist.h.
#
my @wait_event_tranches;
my @wait_event_lwlocks;
my $record_lwlocks = 0;
my $in_tranches = 0;

while (<$wait_event_names>)
{
	chomp;

	# Check for end marker.
	if (/^# END OF PREDEFINED LWLOCKS/)
	{
		$in_tranches = 1;
		next;
	}

	# Skip comments and empty lines.
	next if /^#/;
	next if /^\s*$/;

	# Start recording LWLocks when we find the WaitEventLWLock section.
	if (/^Section: ClassName - WaitEventLWLock$/)
	{
		$record_lwlocks = 1;
		next;
	}

	# Go to the next line if we are not yet recording LWLocks.
	next if not $record_lwlocks;

	# Stop recording if we reach another section.
	last if /^Section:/;

	# Record the LWLock.
	(my $waiteventname, my $waitevendocsentence) = split(/\t/, $_);

	if ($in_tranches)
	{
		push(@wait_event_tranches, $waiteventname);
	}
	else
	{
		push(@wait_event_lwlocks, $waiteventname);
	}
}

#
# While gathering the list of predefined LWLocks, cross-check the lists in
# lwlocklist.h with the wait events we just recorded.
#
my $in_comment = 0;
my $lwlock_count = 0;
my $tranche_count = 0;
while (<$lwlocklist>)
{
	chomp;

	# Skip single-line C comments and empty lines
	next if m{^\s*/\*.*\*/$};
	next if /^\s*$/;

	# skip multiline C comments
	if ($in_comment == 1)
	{
		$in_comment = 0 if m{\*/};
		next;
	}
	elsif (m{^\s*/\*})
	{
		$in_comment = 1;
		next;
	}

	#
	# Gather list of predefined LWLocks and cross-check with the wait events.
	#
	if (/^PG_LWLOCK\((\d+),\s+(\w+)\)$/)
	{
		my ($lockidx, $lockname) = ($1, $2);

		die "lwlocklist.h not in order" if $lockidx < $lastlockidx;
		die "lwlocklist.h has duplicates" if $lockidx == $lastlockidx;

		die "$lockname defined in lwlocklist.h but missing from "
		  . "wait_event_names.txt"
		  if $lwlock_count >= scalar @wait_event_lwlocks;
		die "lists of predefined LWLocks do not match (first mismatch at "
		  . "$wait_event_lwlocks[$lwlock_count] in wait_event_names.txt and "
		  . "$lockname in lwlocklist.h)"
		  if $wait_event_lwlocks[$lwlock_count] ne $lockname;

		$lwlock_count++;

		while ($lastlockidx < $lockidx - 1)
		{
			++$lastlockidx;
		}
		$lastlockidx = $lockidx;

		# Add a "Lock" suffix to each lock name, as the C code depends on that.
		printf $h "#define %-32s (&MainLWLockArray[$lockidx].lock)\n",
		  $lockname . "Lock";

		next;
	}

	#
	# Cross-check the built-in LWLock tranches with the wait events.
	#
	if (/^PG_LWLOCKTRANCHE\((\w+),\s+(\w+)\)$/)
	{
		my ($tranche_id, $tranche_name) = ($1, $2);

		die "$tranche_name defined in lwlocklist.h but missing from "
		  . "wait_event_names.txt"
		  if $tranche_count >= scalar @wait_event_tranches;
		die
		  "lists of built-in LWLock tranches do not match (first mismatch at "
		  . "$wait_event_tranches[$tranche_count] in wait_event_names.txt and "
		  . "$tranche_name in lwlocklist.h)"
		  if $wait_event_tranches[$tranche_count] ne $tranche_name;

		$tranche_count++;

		next;
	}

	die "unable to parse lwlocklist.h line \"$_\"";
}

die
  "$wait_event_lwlocks[$lwlock_count] defined in wait_event_names.txt but "
  . " missing from lwlocklist.h"
  if $lwlock_count < scalar @wait_event_lwlocks;

die
  "$wait_event_tranches[$tranche_count] defined in wait_event_names.txt but "
  . "missing from lwlocklist.h"
  if $tranche_count < scalar @wait_event_tranches;

print $h "\n";
printf $h "#define NUM_INDIVIDUAL_LWLOCKS		%s\n", $lastlockidx + 1;

close $h;

rename($htmp, "$output_path/lwlocknames.h")
  || die "rename: $htmp to $output_path/lwlocknames.h: $!";

close $lwlocklist;
